package com.yifeng.repo.micro.service.server.engine.generate;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
import com.baomidou.mybatisplus.generator.config.po.TableField;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * Created by daibing on 2023/6/2.
 */
@Slf4j
public class MybatisPlusFreemarkerTemplateEngine extends FreemarkerTemplateEngine {
    private final String projectAbsoluteDir;
    private final String resourceName;
    private final boolean mappingStartByModuleName;
    private final boolean enableDynamicSource;
    private final boolean enableGenerateUml;

    public MybatisPlusFreemarkerTemplateEngine(String projectAbsoluteDir, String resourceName, boolean mappingStartByModuleName,
                                               boolean enableDynamicSource, boolean enableGenerateUml) {
        this.projectAbsoluteDir = projectAbsoluteDir;
        this.resourceName = resourceName;
        this.mappingStartByModuleName = mappingStartByModuleName;
        this.enableDynamicSource = enableDynamicSource;
        this.enableGenerateUml = enableGenerateUml;
    }

    @Override
    public AbstractTemplateEngine batchOutput() {
        try {
            ConfigBuilder config = this.getConfigBuilder();
            List<TableInfo> tableInfoList = config.getTableInfoList();

            log.info("代码生成开始，数据库表总计：{}", tableInfoList.size());
            tableInfoList.forEach(tableInfo -> {
                Map<String, Object> objectMap = this.getObjectMap(config, tableInfo);
                Optional.ofNullable(config.getInjectionConfig()).ifPresent(t -> {
                    // 添加自定义属性
                    t.beforeOutputFile(tableInfo, objectMap);
                    // 输出自定义文件
                    outputCustomFile(t.getCustomFiles(), tableInfo, objectMap);
                });
                // custom objectMap
                customObjectMap(tableInfo, objectMap);
                // entity
                outputEntity(tableInfo, objectMap);
                // dto
                outputDto(tableInfo, objectMap, "dto", "Dto");
                outputDto(tableInfo, objectMap, "params", "QueryParam");
                outputDto(tableInfo, objectMap, "params", "CreateParam");
                outputDto(tableInfo, objectMap, "params", "UpdateParam");
                // mapper and xml
                outputMapper(tableInfo, objectMap);
                // service
                outputService(tableInfo, objectMap);
                // manager
                outputManager(tableInfo, objectMap, "manager", "Manager");
                // controller
                outputController(tableInfo, objectMap);
                // plantuml
                outputPlantuml(tableInfo, objectMap, "dto", "Dto");
                log.info("tableName={}, comment={}", tableInfo.getName(), tableInfo.getComment());
            });
        } catch (Exception e) {
            throw new RuntimeException("无法创建文件，请检查配置信息！", e);
        }
        return this;
    }

    private void customObjectMap(TableInfo tableInfo, Map<String, Object> objectMap) {
        // dto等对象参数扩展
        objectMap.put("custom_dto_importPackages", buildDtoImportPackages(tableInfo));
        // manager，controller等扩展
        TableField keyField = findKey(tableInfo);
        objectMap.put("custom_idName", keyField.getPropertyName());
        objectMap.put("custom_idComment", keyField.getComment());
        objectMap.put("custom_idSimpleType", keyField.getColumnType().getType());
        objectMap.put("custom_idGetMethod", "get" + keyField.getCapitalName());
        objectMap.put("custom_offsetIdName", "offset" + keyField.getCapitalName());
        objectMap.put("custom_service_instance_name", tableInfo.getEntityPath().replace("Entity", "") + "Service");
        objectMap.put("custom_Manager_instance_name", tableInfo.getEntityPath().replace("Entity", "") + "Manager");
        objectMap.put("custom_request_mapping", buildRequestMappingUrl(tableInfo, objectMap, resourceName, mappingStartByModuleName));
        objectMap.put("custom_enable_ds", enableDynamicSource);
    }


    /**
     * 输出Dto文件，高仿outputEntity
     */
    private void outputDto(TableInfo tableInfo, Map<String, Object> objectMap, String packageName, String classSuffix) {
        String dtoName = tableInfo.getEntityName().replace("Entity", classSuffix);
        String dtoPath = getPathInfo(OutputFile.parent) + File.separator + packageName;
        if (StringUtils.isNotBlank(dtoName) && StringUtils.isNotBlank(dtoPath)) {
            String templatePrefix = classSuffix.toLowerCase();
            getTemplateFilePath(templateConfig -> templateConfig.getEntity(getConfigBuilder().getGlobalConfig().isKotlin()).replace("entity", templatePrefix)).ifPresent((dto) -> {
                objectMap.put("custom_class_" + classSuffix, dtoName);
                objectMap.put("custom_package_" + packageName, ((Map<?, ?>) (objectMap.get("package"))).get("Parent") + "." + packageName);
                String dtoFile = String.format((dtoPath + File.separator + "%s" + suffixJavaOrKt()), dtoName);
                outputFile(new File(dtoFile), objectMap, dto, getConfigBuilder().getStrategyConfig().entity().isFileOverride());
            });
        }
    }

    /**
     * 输出 Plantuml 文件，高仿outputEntity
     */
    private void outputPlantuml(TableInfo tableInfo, Map<String, Object> objectMap, String packageName, String classSuffix) {
        if (!enableGenerateUml) {
            return;
        }
        String dtoName = tableInfo.getEntityName().replace("Entity", classSuffix);
        String dtoPath = projectAbsoluteDir
                + File.separator + "src"
                + File.separator + "main"
                + File.separator + "resources"
                + File.separator + "uml"
                + File.separator + resourceName;
        if (StringUtils.isNotBlank(dtoName) && StringUtils.isNotBlank(dtoPath)) {
            String templatePrefix = "puml";
            getTemplateFilePath(templateConfig -> templateConfig.getEntity(getConfigBuilder().getGlobalConfig().isKotlin()).replace("entity", templatePrefix)).ifPresent((dto) -> {
                objectMap.put("custom_class_" + classSuffix, dtoName);
                objectMap.put("custom_package_" + packageName, ((Map<?, ?>) (objectMap.get("package"))).get("Parent") + "." + packageName);
                String dtoFile = String.format((dtoPath + File.separator + "%s" + ".puml"), dtoName);
                outputFile(new File(dtoFile), objectMap, dto, getConfigBuilder().getStrategyConfig().entity().isFileOverride());
            });
        }
    }

    /**
     * 输入Manager，高仿Controller
     */
    private void outputManager(TableInfo tableInfo, Map<String, Object> objectMap, String packageName, String classSuffix) {
        String managerName = tableInfo.getControllerName().replace("Controller", classSuffix);
        String managerPath = getPathInfo(OutputFile.parent) + File.separator + packageName;
        if (StringUtils.isNotBlank(managerName) && StringUtils.isNotBlank(managerPath)) {
            String templatePrefix = classSuffix.toLowerCase();
            getTemplateFilePath(templateConfig -> templateConfig.getController().replace("controller", templatePrefix)).ifPresent(manager -> {
                objectMap.put("custom_class_" + classSuffix, managerName);
                objectMap.put("custom_package_" + packageName, ((Map<?, ?>) (objectMap.get("package"))).get("Parent") + "." + packageName);
                String managerFile = String.format((managerPath + File.separator + "%s" + suffixJavaOrKt()), managerName);
                outputFile(new File(managerFile), objectMap, manager, getConfigBuilder().getStrategyConfig().controller().isFileOverride());
            });
        }
    }

    /**
     * 没有主键的表默认以第一列为主键
     */
    private TableField findKey(TableInfo tableInfo) {
        for (TableField field : tableInfo.getFields()) {
            if (field.isKeyFlag()) {
                return field;
            }
        }
        return tableInfo.getFields().get(0);
    }

    private String buildDtoImportPackages(TableInfo tableInfo) {
        StringBuilder sb = new StringBuilder();
        for (String importPackage : tableInfo.getImportPackages()) {
            if (importPackage.startsWith("com.baomidou.mybatisplus.annotation")) {
                continue;
            }
            sb.append("import ").append(importPackage).append(";").append("\n");
        }
        return sb.toString();
    }

    private String buildRequestMappingUrl(TableInfo tableInfo, Map<String, Object> objectMap, String resourceName, boolean mappingStartByModuleName) {
        StringBuilder sb = new StringBuilder();
        String moduleName = String.valueOf(((Map<?, ?>) (objectMap.get("package"))).get("ModuleName"));
        if (mappingStartByModuleName) {
            if (!Strings.isNullOrEmpty(moduleName)) {
                sb.append("/").append(moduleName);
            }
            if (!resourceName.equalsIgnoreCase(moduleName)) {
                sb.append("/").append(resourceName);
            }
        } else {
            if (!resourceName.equalsIgnoreCase(moduleName)) {
                sb.append("/").append(resourceName);
            }
            if (!Strings.isNullOrEmpty(moduleName)) {
                sb.append("/").append(moduleName);
            }
        }
        boolean controllerMappingHyphenStyle = Boolean.parseBoolean(String.valueOf(objectMap.get("controllerMappingHyphenStyle")));
        if (controllerMappingHyphenStyle) {
            sb.append("/").append(String.valueOf(objectMap.get("controllerMappingHyphen")).replace("-entity", ""));
        } else {
            sb.append("/").append(tableInfo.getEntityPath().replace("Entity", ""));
        }
        return sb.toString();
    }
}
